/*
MIT License

Copyright (c) 2018 Meng Rao <raomeng1@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

#pragma once
#include "ptcp_conn.h"
#include "spsc_varq.h"
#include "mmap.h"

namespace tcpshm {

/**
 * @brief TCP/SHM连接类模板
 * 
 * 该类封装了TCP和共享内存两种通信方式的统一接口，
 * 根据运行时配置决定使用哪种通信方式。
 * 
 * @tparam Conf 配置类，包含各种配置参数
 */
template<class Conf>
class TcpShmConnection
{
public:
    /**
     * @brief 获取PTCP文件路径
     * @return std::string PTCP文件的完整路径
     */
    std::string GetPtcpFile() {
        return std::string(ptcp_dir_) + "/" + local_name_ + "_" + remote_name_ + ".ptcp";
    }

    /**
     * @brief 检查连接是否已关闭
     * @return true 连接已关闭
     * @return false 连接仍然有效
     */
    bool IsClosed() {
        return ptcp_conn_.IsClosed();
    }

    /**
     * @brief 请求关闭连接
     * 
     * 此方法不会立即关闭连接，而是向连接发送关闭请求，
     * 实际关闭会在适当的时候进行。
     */
    void Close() {
        ptcp_conn_.RequestClose();
    }

    /**
     * @brief 获取连接关闭原因
     * @param sys_errno 系统错误码输出参数
     * @return const char* 关闭原因字符串
     */
    const char* GetCloseReason(int* sys_errno) {
        return ptcp_conn_.GetCloseReason(sys_errno);
    }

    /**
     * @brief 获取远程端点名称
     * @return char* 远程端点名称
     */
    char* GetRemoteName() {
        return remote_name_;
    }

    /**
     * @brief 获取本地端点名称
     * @return const char* 本地端点名称
     */
    const char* GetLocalName() {
        return local_name_;
    }

    /**
     * @brief 获取PTCP目录路径
     * @return const char* PTCP目录路径
     */
    const char* GetPtcpDir() {
        return ptcp_dir_;
    }

    /**
     * @brief 在发送队列中分配指定大小的消息空间
     * 
     * 分配的空间保证8字节对齐，如果空间不足则返回nullptr。
     * 根据通信方式（SHM或TCP）选择相应的队列进行分配。
     * 
     * @param size 消息大小（包括头部）
     * @return MsgHeader* 消息头部指针，如果空间不足则返回nullptr
     */
    MsgHeader* Alloc(uint16_t size) {
        if(shm_sendq_) return shm_sendq_->Alloc(size);
        return ptcp_conn_.Alloc(size);
    }

    /**
     * @brief 提交通过Alloc()分配的最后一个消息并发送
     * 
     * 对于SHM模式，直接推送消息到接收方；
     * 对于TCP模式，将消息推送到网络发送队列。
     */
    void Push() {
        if(shm_sendq_)
            shm_sendq_->Push();
        else
            ptcp_conn_.Push();
    }

    /**
     * @brief 提交消息但不立即发送（仅适用于TCP模式）
     * 
     * 对于SHM模式，行为与Push()相同；
     * 对于TCP模式，不会立即发送消息，因为可能还有更多消息需要推送。
     */
    void PushMore() {
        if(shm_sendq_)
            shm_sendq_->Push();
        else
            ptcp_conn_.PushMore();
    }

    /**
     * @brief 从接收队列获取下一条消息
     * 
     * 返回的消息地址保证8字节对齐，如果队列为空则返回nullptr。
     * 如果调用者之后不调用Pop()，则下次调用会得到相同的消息。
     * 用户通常不需要直接调用此方法，因为轮询函数会处理它。
     * 
     * @return MsgHeader* 消息头部指针，如果队列为空则返回nullptr
     */
    MsgHeader* Front() {
        if(shm_recvq_) return shm_recvq_->Front();
        return ptcp_conn_.Front();
    }

    /**
     * @brief 消费通过Front()或轮询函数获取的消息
     * 
     * 调用此方法后，Front()将返回下一条消息（如果存在）。
     */
    void Pop() {
        if(shm_recvq_)
            shm_recvq_->Pop();
        else
            ptcp_conn_.Pop();
    }

    /// 用户自定义连接数据
    typename Conf::ConnectionUserData user_data;

private:
    // 友元声明，允许TcpShmClient和TcpShmServer访问私有成员
    template<class T1, class T2>
    friend class TcpShmClient;
    template<class T1, class T2>
    friend class TcpShmServer;

    /**
     * @brief 构造函数
     * 
     * 初始化远程名称为空字符串。
     */
    TcpShmConnection() {
        remote_name_[0] = 0;
    }

    /**
     * @brief 初始化连接
     * @param ptcp_dir PTCP文件目录
     * @param local_name 本地名称
     */
    void init(const char* ptcp_dir, const char* local_name) {
        ptcp_dir_ = ptcp_dir;
        local_name_ = local_name;
    }

    /**
     * @brief 打开通信文件
     * 
     * 根据use_shm参数决定打开共享内存文件还是PTCP文件。
     * 
     * @param use_shm 是否使用共享内存
     * @param error_msg 错误信息输出参数
     * @return true 打开成功
     * @return false 打开失败
     */
    bool OpenFile(bool use_shm, const char** error_msg) {
        if(use_shm) {
            // 构造共享内存文件路径
            std::string shm_send_file = std::string("/") + local_name_ + "_" + remote_name_ + ".shm";
            std::string shm_recv_file = std::string("/") + remote_name_ + "_" + local_name_ + ".shm";
            
            // 打开发送队列共享内存文件
            if(!shm_sendq_) {
                shm_sendq_ = my_mmap<SHMQ>(shm_send_file.c_str(), true, error_msg);
                if(!shm_sendq_) return false;
            }
            
            // 打开接收队列共享内存文件
            if(!shm_recvq_) {
                shm_recvq_ = my_mmap<SHMQ>(shm_recv_file.c_str(), true, error_msg);
                if(!shm_recvq_) return false;
            }
            return true;
        }
        
        // 使用TCP模式，打开PTCP文件
        std::string ptcp_send_file = GetPtcpFile();
        return ptcp_conn_.OpenFile(ptcp_send_file.c_str(), error_msg);
    }

    /**
     * @brief 获取序列号信息
     * 
     * @param local_ack_seq 本地确认序列号输出参数
     * @param local_seq_start 本地序列号起始值输出参数
     * @param local_seq_end 本地序列号结束值输出参数
     * @param error_msg 错误信息输出参数
     * @return true 获取成功
     * @return false 获取失败
     */
    bool GetSeq(uint32_t* local_ack_seq, uint32_t* local_seq_start, uint32_t* local_seq_end, const char** error_msg) {
        // 如果使用SHM模式，直接返回成功
        if(shm_sendq_) return true;
        
        // 如果使用TCP模式，从PTCP连接获取序列号
        if(!ptcp_conn_.GetSeq(local_ack_seq, local_seq_start, local_seq_end)) {
            *error_msg = "Ptcp file corrupt";
            errno = 0;
            return false;
        }
        return true;
    }

    /**
     * @brief 重置连接
     * 
     * 清空发送和接收队列的数据。
     */
    void Reset() {
        if(shm_sendq_) {
            // 重置SHM队列
            memset(shm_sendq_, 0, sizeof(SHMQ));
            memset(shm_recvq_, 0, sizeof(SHMQ));
        }
        else {
            // 重置PTCP连接
            ptcp_conn_.Reset();
        }
    }

    /**
     * @brief 释放连接资源
     * 
     * 取消内存映射并释放相关资源。
     */
    void Release() {
        remote_name_[0] = 0;
        if(shm_sendq_) {
            my_munmap<SHMQ>(shm_sendq_);
            shm_sendq_ = nullptr;
        }
        if(shm_recvq_) {
            my_munmap<SHMQ>(shm_recvq_);
            shm_recvq_ = nullptr;
        }
        ptcp_conn_.Release();
    }

    /**
     * @brief 打开连接
     * @param sock_fd 套接字文件描述符
     * @param remote_ack_seq 远程确认序列号
     * @param now 当前时间戳
     */
    void Open(int sock_fd, uint32_t remote_ack_seq, int64_t now) {
        ptcp_conn_.Open(sock_fd, remote_ack_seq, now);
    }

    /**
     * @brief 尝试关闭文件描述符
     * @return true 关闭成功
     * @return false 关闭失败或不需要关闭
     */
    bool TryCloseFd() {
        return ptcp_conn_.TryCloseFd();
    }

    /**
     * @brief TCP前端处理
     * 
     * 发送心跳包并获取前端消息。
     * 
     * @param now 当前时间戳
     * @return MsgHeader* 消息头部指针
     */
    MsgHeader* TcpFront(int64_t now) {
        ptcp_conn_.SendHB(now);
        return ptcp_conn_.Front(); // 对于SHM，我们需要接收心跳包，Front()总是返回nullptr
    }

    /**
     * @brief SHM前端处理
     * 
     * 从SHM接收队列获取前端消息。
     * 
     * @return MsgHeader* 消息头部指针
     */
    MsgHeader* ShmFront() {
        return shm_recvq_->Front();
    }

private:
    const char* local_name_;                     ///< 本地名称
    char remote_name_[Conf::NameSize];           ///< 远程名称
    const char* ptcp_dir_ = nullptr;             ///< PTCP目录路径
    PTCPConnection<Conf> ptcp_conn_;             ///< PTCP连接对象
    using SHMQ = SPSCVarQueue<Conf::ShmQueueSize>;  ///< SHM队列类型别名
    alignas(64) SHMQ* shm_sendq_ = nullptr;      ///< SHM发送队列（64字节对齐）
    SHMQ* shm_recvq_ = nullptr;                  ///< SHM接收队列
};
} // namespace tcpshm